package libs.game.sokoban;

import libs.game.*;
import libs.game.sokoban.SokobanGamePlan;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 *              It represents a path where the boxes can be to push them to the goal.
 * 
 * @author      Jan Papousek
 * @version     2009-03-12
 */
public class GoalPath extends AbstractPath {

    /**
     *          It creates a path to the goal.
     *
     * @param   gamePlan    Game plan.
     * @param   goal        Goal coordinates.
     * @param   boxPaths    Box Paths.
     * @throws  IllegalArgumentException if the field with goal coordinates
     *          does not contain a goal.
     */
    public GoalPath(SokobanGamePlan gamePlan, Coordinates goal, Map<Coordinates,Path> boxPaths) {
        if (gamePlan == null) {
            throw new NullPointerException("Argument 'gamePlan' is null.");
        }
        if (goal == null) {
            throw new NullPointerException("Argument 'goal' is null.");
        }
        if ((gamePlan.getField(goal) != SokobanGamePlan.FIELD_BOX_ON_GOAL)
            && (gamePlan.getField(goal) != SokobanGamePlan.FIELD_PERSON_ON_GOAL)
            && (gamePlan.getField(goal) != SokobanGamePlan.FIELD_GOAL)){
                throw new IllegalArgumentException("The field does not contain goal.");
        }
        path = new Integer[gamePlan.rows()][gamePlan.columns()];
        compute(gamePlan, goal, goal, 0, boxPaths);
        path[goal.getY()][goal.getX()] = 0;
    }


    /**
     *          It marks a goal path on the field
     *          and by recursion on the neighbour fields.
     *
     * @param   gamePlan        Game plan.
     * @param   field           Coordinates of the marked field.
     * @param   hasToBeEmpty    Coordinates of the field which has to be empty
     *                          to push the box.
     * @param   value           Path value which I want to mark the field with.
     * @param   boxPaths        Box Paths.
     * @return  TRUE if the path mark of the field was changed,
     *          otherwise FALSE.
     */
    private boolean compute(SokobanGamePlan gamePlan, Coordinates field, Coordinates hasToBeEmpty, int value, Map<Coordinates,Path> boxPaths)  {
        if ((hasToBeEmpty.getX() < 0) || (hasToBeEmpty.getY() < 0) ||
                (hasToBeEmpty.getY() >= gamePlan.rows()) ||
                (hasToBeEmpty.getX() >= gamePlan.columns())
           ) {
            return false;
        }
        if (path[field.getY()][field.getX()] != null) {
            return false;
        }
        switch (gamePlan.getField(hasToBeEmpty)) {
            default:
                return false;
            case SokobanGamePlan.FIELD_BOX_ON_EMPTY:
            case SokobanGamePlan.FIELD_BOX_ON_GOAL:
            case SokobanGamePlan.FIELD_EMPTY:
            case SokobanGamePlan.FIELD_GOAL:
            case SokobanGamePlan.FIELD_PERSON_ON_EMPTY:
            case SokobanGamePlan.FIELD_PERSON_ON_GOAL:
                numOfPathFields++;
                path[field.getY()][field.getX()] = value;
                this.compute(
                        gamePlan,
                        new Coordinates(field.getX()-1,field.getY()),
                        new Coordinates(field.getX()-2,field.getY()),
                        value+1,
                        boxPaths
                );
                this.compute(
                        gamePlan,
                        new Coordinates(field.getX()+1,field.getY()),
                        new Coordinates(field.getX()+2,field.getY()),
                        value+1,
                        boxPaths
                );
                this.compute(
                        gamePlan,
                        new Coordinates(field.getX(),field.getY()-1),
                        new Coordinates(field.getX(),field.getY()-2),
                        value+1,
                        boxPaths
                );
                this.compute(
                        gamePlan,
                        new Coordinates(field.getX(),field.getY()+1),
                        new Coordinates(field.getX(),field.getY()+2),
                        value+1,
                        boxPaths
                );
        }

        return true;
    }

}
